﻿using System;
using System.Windows;

namespace AIStudio.Wpf.DiagramDesigner.Geometrys
{
    [Serializable]
    public struct SizeBase : IFormattable
    {
        public static SizeBase Zero { get; } = new SizeBase(0, 0);

        public SizeBase(double width, double height)
        {
            if (width < 0 || height < 0)
            {
                throw new System.ArgumentException("Size_WidthAndHeightCannotBeNegative");
            }

            _width = width;
            _height = height;
        }

        public bool IsEmpty
        {
            get
            {
                return _width < 0;
            }
        }

        internal double _width;

        public double Width
        {
            get
            {
                return _width;
            }
            set
            {
                if (IsEmpty)
                {
                    throw new System.InvalidOperationException("Size_CannotModifyEmptySize");
                }

                if (value < 0)
                {
                    throw new System.ArgumentException("Size_WidthCannotBeNegative");
                }

                _width = value;
            }
        }

        internal double _height;
        public double Height
        {
            get
            {
                return _height;
            }
            set
            {
                if (IsEmpty)
                {
                    throw new System.InvalidOperationException("Size_CannotModifyEmptySize");
                }

                if (value < 0)
                {
                    throw new System.ArgumentException("Size_HeightCannotBeNegative");
                }

                _height = value;
            }
        }

        public SizeBase Add(double value) => new SizeBase(Width + value, Height + value);

        public SizeBase Add(double width, double height) => new SizeBase(Width + width, Height + height);

        //public bool Equals(Size size) => size != null && Width == size.Width && Height == size.Height;

        //public override string ToString() => $"Size(width={Width}, height={Height})";

        public static implicit operator SizeBase(Size size)
        {
            return new SizeBase(size.Width, size.Height);
        }

        public static implicit operator Size(SizeBase sizebase)
        {
            return new Size(sizebase.Width, sizebase.Height);
        }

        #region Statics

        /// <summary>
        /// Empty - a static property which provides an Empty size.  Width and Height are 
        /// negative-infinity.  This is the only situation
        /// where size can be negative.
        /// </summary>
        public static SizeBase Empty
        {
            get
            {
                return s_empty;
            }
        }

        #endregion Statics

        #region Public Methods




        /// <summary>
        /// Compares two Size instances for exact equality.
        /// Note that double values can acquire error when operated upon, such that
        /// an exact comparison between two values which are logically equal may fail.
        /// Furthermore, using this equality operator, Double.NaN is not equal to itself.
        /// </summary>
        /// <returns>
        /// bool - true if the two Size instances are exactly equal, false otherwise
        /// </returns>
        /// <param name='size1'>The first Size to compare</param>
        /// <param name='size2'>The second Size to compare</param>
        public static bool operator ==(SizeBase size1, SizeBase size2)
        {
            return size1.Width == size2.Width &&
                   size1.Height == size2.Height;
        }

        /// <summary>
        /// Compares two Size instances for exact inequality.
        /// Note that double values can acquire error when operated upon, such that
        /// an exact comparison between two values which are logically equal may fail.
        /// Furthermore, using this equality operator, Double.NaN is not equal to itself.
        /// </summary>
        /// <returns>
        /// bool - true if the two Size instances are exactly unequal, false otherwise
        /// </returns>
        /// <param name='size1'>The first Size to compare</param>
        /// <param name='size2'>The second Size to compare</param>
        public static bool operator !=(SizeBase size1, SizeBase size2)
        {
            return !(size1 == size2);
        }
        /// <summary>
        /// Compares two Size instances for object equality.  In this equality
        /// Double.NaN is equal to itself, unlike in numeric equality.
        /// Note that double values can acquire error when operated upon, such that
        /// an exact comparison between two values which
        /// are logically equal may fail.
        /// </summary>
        /// <returns>
        /// bool - true if the two Size instances are exactly equal, false otherwise
        /// </returns>
        /// <param name='size1'>The first Size to compare</param>
        /// <param name='size2'>The second Size to compare</param>
        public static bool Equals(SizeBase size1, SizeBase size2)
        {
            if (size1.IsEmpty)
            {
                return size2.IsEmpty;
            }
            else
            {
                return size1.Width.Equals(size2.Width) &&
                       size1.Height.Equals(size2.Height);
            }
        }

        /// <summary>
        /// Equals - compares this Size with the passed in object.  In this equality
        /// Double.NaN is equal to itself, unlike in numeric equality.
        /// Note that double values can acquire error when operated upon, such that
        /// an exact comparison between two values which
        /// are logically equal may fail.
        /// </summary>
        /// <returns>
        /// bool - true if the object is an instance of Size and if it's equal to "this".
        /// </returns>
        /// <param name='o'>The object to compare to "this"</param>
        public override bool Equals(object o)
        {
            if ((null == o) || !(o is SizeBase))
            {
                return false;
            }

            SizeBase value = (SizeBase)o;
            return SizeBase.Equals(this, value);
        }

        /// <summary>
        /// Equals - compares this Size with the passed in object.  In this equality
        /// Double.NaN is equal to itself, unlike in numeric equality.
        /// Note that double values can acquire error when operated upon, such that
        /// an exact comparison between two values which
        /// are logically equal may fail.
        /// </summary>
        /// <returns>
        /// bool - true if "value" is equal to "this".
        /// </returns>
        /// <param name='value'>The Size to compare to "this"</param>
        public bool Equals(SizeBase value)
        {
            return SizeBase.Equals(this, value);
        }
        /// <summary>
        /// Returns the HashCode for this Size
        /// </summary>
        /// <returns>
        /// int - the HashCode for this Size
        /// </returns>
        public override int GetHashCode()
        {
            if (IsEmpty)
            {
                return 0;
            }
            else
            {
                // Perform field-by-field XOR of HashCodes
                return Width.GetHashCode() ^
                       Height.GetHashCode();
            }
        }

        /// <summary>
        /// Parse - returns an instance converted from the provided string using
        /// the culture "en-US"
        /// <param name="source"> string with Size data </param>
        /// </summary>
        //public static Size Parse(string source)
        //{
        //    IFormatProvider formatProvider = System.Windows.Markup.TypeConverterHelper.InvariantEnglishUS;

        //    TokenizerHelper th = new TokenizerHelper(source, formatProvider);

        //    Size value;

        //    String firstToken = th.NextTokenRequired();

        //    // The token will already have had whitespace trimmed so we can do a
        //    // simple string compare.
        //    if (firstToken == "Empty")
        //    {
        //        value = Empty;
        //    }
        //    else
        //    {
        //        value = new Size(
        //            Convert.ToDouble(firstToken, formatProvider),
        //            Convert.ToDouble(th.NextTokenRequired(), formatProvider));
        //    }

        //    // There should be no more tokens in this string.
        //    th.LastTokenRequired();

        //    return value;
        //}

        #endregion Public Methods

        #region Public Operators

        /// <summary>
        /// Explicit conversion to Vector.
        /// </summary>
        /// <returns>
        /// Vector - A Vector equal to this Size
        /// </returns>
        /// <param name="size"> Size - the Size to convert to a Vector </param>
        public static explicit operator VectorBase(SizeBase size)
        {
            return new VectorBase(size._width, size._height);
        }

        /// <summary>
        /// Explicit conversion to PointBase
        /// </summary>
        /// <returns>
        /// PointBase - A PointBase equal to this Size
        /// </returns>
        /// <param name="size"> Size - the Size to convert to a PointBase </param>
        public static explicit operator PointBase(SizeBase size)
        {
            return new PointBase(size._width, size._height);
        }

        #endregion Public Operators

        #region Private Methods

        static private SizeBase CreateEmptySize()
        {
            SizeBase size = new SizeBase();
            // We can't set these via the property setters because negatives widths
            // are rejected in those APIs.
            size._width = Double.NegativeInfinity;
            size._height = Double.NegativeInfinity;
            return size;
        }

        #endregion Private Methods

        #region Private Fields

        private readonly static SizeBase s_empty = CreateEmptySize();

        #endregion Private Fields

        #region Internal Properties


        /// <summary>
        /// Creates a string representation of this object based on the current culture.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        public override string ToString()
        {
            // Delegate to the internal method which implements all ToString calls.
            return ConvertToString(null /* format string */, null /* format provider */);
        }

        /// <summary>
        /// Creates a string representation of this object based on the IFormatProvider
        /// passed in.  If the provider is null, the CurrentCulture is used.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        public string ToString(IFormatProvider provider)
        {
            // Delegate to the internal method which implements all ToString calls.
            return ConvertToString(null /* format string */, provider);
        }

        /// <summary>
        /// Creates a string representation of this object based on the format string
        /// and IFormatProvider passed in.
        /// If the provider is null, the CurrentCulture is used.
        /// See the documentation for IFormattable for more information.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        string IFormattable.ToString(string format, IFormatProvider provider)
        {
            // Delegate to the internal method which implements all ToString calls.
            return ConvertToString(format, provider);
        }

        /// <summary>
        /// Creates a string representation of this object based on the format string
        /// and IFormatProvider passed in.
        /// If the provider is null, the CurrentCulture is used.
        /// See the documentation for IFormattable for more information.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        internal string ConvertToString(string format, IFormatProvider provider)
        {
            if (IsEmpty)
            {
                return "Empty";
            }

            // Helper to get the numeric list separator for a given culture.
            char separator = ',';
            return String.Format(provider,
                                 "{1:" + format + "}{0}{2:" + format + "}",
                                 separator,
                                 _width,
                                 _height);
        }



        #endregion Internal Properties
    }
}